/*
 * Copyright (c) 2016, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/****************************************************************************************************************
                   INCLUDES
****************************************************************************************************************/

/* Standard includes                                                          */
#include <unistd.h>

/* SimpleLink includes                                                        */
#include <ti/drivers/net/wifi/simplelink.h>

/* MQTT Library includes                                                      */
#include <ti/net/mqtt/mqttclient.h>

/* POSIX includes                                                             */
#include "pthread.h"

/* TI-Drivers includes                                                        */
#include <ti/display/Display.h>

/* Application includes                                                       */
#include "Board.h"

#include "wifi_doorlock_app.h"
#include "mqtt_client_task.h"
#include "motor_driver_if.h"

/****************************************************************************************************************
                   DEFINES
****************************************************************************************************************/
/* Define to support NTP */
#define NTP_START_YEAR              (1900)
#define CC32XX_MONTH_START_OFFSET   (1)

/* Operate Lib in MQTT 3.1 mode.                                              */
#define MQTT_3_1_1               false
#define MQTT_3_1                 true

#define WILL_TOPIC               "Client"
#define WILL_MSG                 "Client Stopped"
#define WILL_QOS                 MQTT_QOS_2
#define WILL_RETAIN              false

#define SECURE_CLIENT

/* Defining Broker IP address and port Number                                 */
#define SERVER_ADDRESS           "m2m.eclipse.org"
#define SERVER_IP_ADDRESS        "198.41.30.241"
#define PORT_NUMBER              1883
#define SECURED_PORT_NUMBER      8883
#define LOOPBACK_PORT            1882

/* Clean session flag                                                         */
#define CLEAN_SESSION            true

/* Retain Flag. Used in publish message.                                      */
#define RETAIN_ENABLE            1

#define LOCK_CONTROL_CMD_LENGTH  5

#define TOPIC_START_INDEX       (12)

#define CLIENT_NUM_SECURE_FILES 1

/****************************************************************************************************************
                   LOCAL FUNCTION PROTOTYPES
****************************************************************************************************************/
void *  MqttClientThread(void * pvParameters);
int32_t MqttClient_start();
void    Mqtt_ClientStop(uint8_t disconnect);

/****************************************************************************************************************
                   GLOBAL VARIABLES
****************************************************************************************************************/

/* MQTT RX task handle                                                        */
pthread_t g_rx_task_hndl        = (pthread_t) NULL;

/* Overall Application App Context                                            */
extern Application_CB App_CB;
extern Display_Handle display;

/* Struct with parameters for MQTT connection                                 */
MQTTClient_Params  MqttClientExample_params;

/* Client ID                                                                  */
/* If ClientId isn't set, the MAC address of the device will be copied into   */
/* the ClientID parameter.                                                    */
char ClientId[13] = {'\0'};

/* Subscription topics and qos values                                         */
char *topic[SUBSCRIPTION_TOPIC_COUNT] =
        { SUBSCRIPTION_TOPIC0, SUBSCRIPTION_TOPIC1, \
          SUBSCRIPTION_TOPIC2, SUBSCRIPTION_TOPIC3 };

unsigned char qos[SUBSCRIPTION_TOPIC_COUNT] =
        { MQTT_QOS_2, MQTT_QOS_2, MQTT_QOS_2, MQTT_QOS_2 };

/* Publishing topics and messages                                             */
char *publish_topic[PUBLISH_TOPIC_COUNT] = { PUBLISH_TOPIC0 };

char *publish_data[PUBLISH_DATA_COUNT] = { PUBLISH_TOPIC0_LOCKED, PUBLISH_TOPIC0_UNLOCKED };

#ifdef  SECURE_CLIENT
char *Mqtt_Client_secure_files[CLIENT_NUM_SECURE_FILES] = {"eclipsecert.der"};

/* Initialization structure to be used with sl_ExtMqtt_Init API. In order to  */
/* use secured socket method, the flag MQTTCLIENT_NETCONN_SEC, cipher,        */
/* n_files and secure_files must be configured.                               */
/* certificates also must be programmed  ("ca-cert.pem").                     */
/* The first parameter is a bit mask which configures server address type and */
/* security mode.                                                             */
/* Server address type: IPv4, IPv6 and URL must be declared with The          */
/* corresponding flag.                                                        */
/* Security mode: The flag MQTTCLIENT_NETCONN_SEC enables the security (TLS)  */
/* which includes domain name verification and certificate catalog            */
/* verification, those verifications can be disabled by adding to the bit mask*/
/* MQTTCLIENT_NETCONN_SKIP_DOMAIN_NAME_VERIFICATION and                       */
/* MQTTCLIENT_NETCONN_SKIP_CERTIFICATE_CATALOG_VERIFICATION flags             */
/* Example: MQTTCLIENT_NETCONN_IP6 | MQTTCLIENT_NETCONN_SEC |                 */
/* MQTTCLIENT_NETCONN_SKIP_CERTIFICATE_CATALOG_VERIFICATION                   */
/* For this bit mask, the IPv6 address type will be in use, the security      */
/* feature will be enable and the certificate catalog verification will be    */
/* skipped.                                                                   */
/* Note: The domain name verification requires URL Server address type        */
/*       otherwise, this verification will be disabled.                       */
MQTTClient_ConnParams Mqtt_ClientCtx =
{
     MQTTCLIENT_NETCONN_IP4 | MQTTCLIENT_NETCONN_SEC | MQTT_DEV_NETCONN_OPT_SKIP_CERTIFICATE_CATALOG_VERIFICATION | MQTTCLIENT_NETCONN_SKIP_DOMAIN_NAME_VERIFICATION,
     SERVER_IP_ADDRESS,
     SECURED_PORT_NUMBER,
     SLNETSOCK_SEC_METHOD_SSLv3_TLSV1_2,
     SLNETSOCK_SEC_CIPHER_FULL_LIST,
     CLIENT_NUM_SECURE_FILES,
     Mqtt_Client_secure_files
};
#else
MQTTClient_ConnParams Mqtt_ClientCtx =
{
    MQTTCLIENT_NETCONN_URL,
    SERVER_ADDRESS,
    PORT_NUMBER, 0, 0, 0,
    NULL
};
#endif

/* Initialize the will_param structure to the default will parameters         */
MQTTClient_Will will_param =
{
    WILL_TOPIC,
    WILL_MSG,
    WILL_QOS,
    WILL_RETAIN
};

uint16_t    mqttKeepAliveTimeout = 43200; // 43200 sec = 12hr * 60 min * 60 sec

//*****************************************************************************
//
//! Task implementing MQTT Server plus client bridge
//!
//! This function
//!    1. Initializes network driver and connects to the default AP
//!    2. Initializes the mqtt client ans server libraries and set up MQTT
//!       with the remote broker.
//!    3. set up the button events and their callbacks(for publishing)
//!    4. handles the callback signals
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
void * MqttClient(void *pvParameters)
{
    long lRetVal = -1;
    char *tmpBuff;
    char recvdMsg[256];
    int32_t iCount = 0;
    mqtt_task_states_t    mqttState = MQTT_LIB_INITIALIZATION;
    struct msgQueue queueElemRecv;

    memset(recvdMsg, 0x00, sizeof(recvdMsg));

    sem_post(&App_CB.mqttReady);
    sem_wait(&App_CB.startBarrier);

    while(1)
    {
        switch(mqttState)
        {
        case MQTT_LIB_INITIALIZATION:
        {
            lRetVal = MqttClient_start();
            if (lRetVal == -1)
            {
                Display_printf(
                        display,
                        0,
                        0,
                        "\t [MQTT Thread] MQTT Client library initialization failed");
                while (1);
            }
            else
            {
                Display_printf(
                        display,
                        0,
                        0,
                        "[MQTT Thread] MQTT Client library initialized successfully");
                SET_STATUS_BIT(App_CB.status, AppStatusBits_MqttInitialized);

                if(GET_STATUS_BIT(App_CB.status, AppStatusBits_Connection) && GET_STATUS_BIT(App_CB.status, AppStatusBits_IpAcquired))
                {
                    mqttState = MQTT_CLIENT_CONNECTING_TO_BROKER;
                }
                else
                {
                    mqttState = MQTT_WAITING_FOR_NETWORK_CONNECTION;
                }
            }
        }
            break;
        case MQTT_WAITING_FOR_NETWORK_CONNECTION:
        {
            sem_wait(&App_CB.connectionDoneSignal);
            mqttState = MQTT_CLIENT_CONNECTING_TO_BROKER;
        }
            break;
        case MQTT_CLIENT_CONNECTING_TO_BROKER:
        {

#if CLEAN_SESSION == false
            bool clean = CLEAN_SESSION;
            MQTTClient_set(App_CB.mqttClientHandle, MQTT_CLIENT_CLEAN_CONNECT, (void *)&clean, sizeof(bool));
#endif
            /* The return code of MQTTClient_connect is the ConnACK value that
             returns from the server */
            lRetVal = MQTTClient_connect(App_CB.mqttClientHandle);

            /* negative lRetVal means error,
             0 means connection successful without session stored by the server,
             greater than 0 means successful connection with session stored by
             the server */
            if (0 > lRetVal)
            {
                /* lib initialization failed                                      */
                Display_printf(
                        display,
                        0,
                        0,
                        "\t [MQTT Thread] Connection to broker failed, Error code: %d",
                        lRetVal);

                MQTTClient_disconnect(App_CB.mqttClientHandle);
                SET_STATUS_BIT(App_CB.status, AppStatusBits_MqttStopInProgress);
                Mqtt_ClientStop(1);
                while (GET_STATUS_BIT(App_CB.status,
                                      AppStatusBits_MqttStopInProgress))
                {
                    sleep(1);
                }
                mqttState = MQTT_LIB_INITIALIZATION;
                sleep(1);
            }
            else
            {
                SET_STATUS_BIT(App_CB.status, AppStatusBits_MqttConnected);
                mqttState = MQTT_CLIENT_CONNECTED;
            }
            /* Subscribe to topics when session is not stored by the server       */
            if (GET_STATUS_BIT(App_CB.status, AppStatusBits_MqttConnected)
                    && (0 == lRetVal))
            {
                uint8_t subIndex;
                MQTTClient_SubscribeParams subscriptionInfo[SUBSCRIPTION_TOPIC_COUNT];

                for (subIndex = 0; subIndex < SUBSCRIPTION_TOPIC_COUNT;
                        subIndex++)
                {
                    subscriptionInfo[subIndex].topic = topic[subIndex];
                    subscriptionInfo[subIndex].qos = qos[subIndex];
                }

                if (MQTTClient_subscribe(App_CB.mqttClientHandle,
                                         subscriptionInfo,
                                         SUBSCRIPTION_TOPIC_COUNT) < 0)
                {
                    Display_printf(display, 0, 0,
                                   "\t [MQTT Thread] Subscription Error");
                    MQTTClient_disconnect(App_CB.mqttClientHandle);

                    /* Manually disconnected, need to reconnect to broker*/
                    SET_STATUS_BIT(App_CB.status,
                                   AppStatusBits_MqttStopInProgress);
                    Mqtt_ClientStop(1);
                    while (GET_STATUS_BIT(App_CB.status,
                                          AppStatusBits_MqttStopInProgress))
                    {
                        sleep(1);
                    }
                    mqttState = MQTT_LIB_INITIALIZATION;
                }
                else
                {
                    for (iCount = 0; iCount < SUBSCRIPTION_TOPIC_COUNT;
                            iCount++)
                    {
                        Display_printf(display, 0, 0,
                                       "[MQTT Thread] Client subscribed on %s",
                                       topic[iCount]);
                    }
                }
            }
        }
            break;
        case MQTT_CLIENT_CONNECTED:
        {
            /* handling the signals from various callbacks including the push button  */
            /* prompting the client to publish a msg on PUB_TOPIC OR msg received by  */
            /* the server on enrolled topic(for which the on-board client ha enrolled)*/
            /* from a local client(will be published to the remote broker by the      */
            /* client) OR msg received by the client from the remote broker (need to  */
            /* be sent to the server to see if any local client has subscribed on the */
            /* same topic).                                                           */

            /* waiting for signals                                                */
            mq_receive(App_CB.mqttQueue, (char*) &queueElemRecv,
                       sizeof(struct msgQueue), NULL);

            switch (queueElemRecv.event)
            {
            /* msg received by client from remote broker (on a topic      */
            /* subscribed by local client)                                */
            case MSG_RECV_BY_CLIENT:
                tmpBuff = (char *) ((char *) queueElemRecv.msgPtr
                        + TOPIC_START_INDEX);
                if (strncmp(tmpBuff, (const char*) topic[1],
                            queueElemRecv.topLen) == 0)
                {
                    struct motorMsg queueElem;

                    strncpy(recvdMsg,
                            ((char*) queueElemRecv.msgPtr + TOPIC_START_INDEX
                                    + queueElemRecv.topLen + 1),
                            queueElemRecv.msgLen);
                    recvdMsg[queueElemRecv.msgLen] = '\0';
                    if ((strncmp(recvdMsg, "lock", sizeof(queueElemRecv.msgLen))
                            == 0) && !App_CB.locked)
                    {
                        queueElem.event = LOCK_MOTOR_CMD;
                        App_CB.locked = true;
                        if (Motor_SendMsgToQueue(&queueElem))
                        {
                            Display_printf(
                                    display, 0, 0,
                                    "\t [MQTT Thread] Queue is full");
                        }
                    }
                    else if ((strncmp(recvdMsg, "unlock",
                                      sizeof(queueElemRecv.msgLen)) == 0) && App_CB.locked)
                    {
                        queueElem.event = UNLOCK_MOTOR_CMD;
                        App_CB.locked = false;
                        if (Motor_SendMsgToQueue(&queueElem))
                        {
                            Display_printf(
                                    display, 0, 0,
                                    "\t [MQTT Thread] Queue is full");
                        }
                    }
                    else
                    {
                        Display_printf(display, 0, 0,
                                       "[MQTT Thread] Message: %s",
                                       recvdMsg);
                        Display_printf(
                                display,
                                0,
                                0,
                                "[MQTT Thread] Message ignored because lock is already in requested state or payload is invalid");
                        break;
                    }
                }
                else if (strncmp(tmpBuff, (const char*) topic[2],
                                 queueElemRecv.topLen) == 0)
                {
                    char msg[30] = { '\0' };

                    strcpy(msg,
                           (App_CB.locked == true) ?
                           PUBLISH_TOPIC0_LOCKED :
                                                     PUBLISH_TOPIC0_UNLOCKED);

                    lRetVal = MQTTClient_publish(
                            App_CB.mqttClientHandle, (char*) publish_topic[0],
                            strlen((char*) publish_topic[0]), (char*) msg,
                            strlen((char*) msg),
                            MQTT_QOS_2 | ((RETAIN_ENABLE) ?
                            MQTT_PUBLISH_RETAIN :
                                                            0));

                    Display_printf(
                            display,
                            0,
                            0,
                            "\n\r [MQTT Thread] CC3200 Publishes the following message");
                    Display_printf(display, 0, 0, "[MQTT Thread] Topic: %s",
                                   publish_topic);
                    Display_printf(display, 0, 0, "[MQTT Thread] Data: %s",
                                   msg);
                }
                else if (strncmp(tmpBuff, (const char*) topic[3],
                                 queueElemRecv.topLen) == 0)
                {
                    struct controlMsg controlMsg;

                    controlMsg.threadID = 1;
                    controlMsg.msg = CONTROL_MSG_OTA_START;

                    if (!GET_STATUS_BIT(App_CB.status,
                                        AppStatusBits_OtaInProgress))
                    {
                        mq_send(App_CB.controlQueue, (char *) &controlMsg,
                                sizeof(struct controlMsg), NULL);

                        SET_STATUS_BIT(App_CB.status,
                                       AppStatusBits_OtaInProgress);
                    }
                }

                free(queueElemRecv.msgPtr);
                break;

                /* On-board client disconnected from remote broker, only      */
                /* local MQTT network will work                               */
            case LOCAL_CLIENT_DISCONNECTION:
                Display_printf(display, 0, 0,
                               "\t [MQTT Thread] On-board Client Disconnected");

                if(GET_STATUS_BIT(App_CB.status, AppStatusBits_Connection) && GET_STATUS_BIT(App_CB.status, AppStatusBits_IpAcquired))
                {
                    MQTTClient_disconnect(App_CB.mqttClientHandle);
                    CLR_STATUS_BIT(App_CB.status, AppStatusBits_MqttConnected);
                    mqttState = MQTT_CLIENT_CONNECTING_TO_BROKER;
                }
                else
                {
                    Mqtt_ClientStop(1);
                    mqttState = MQTT_LIB_INITIALIZATION;
                }
                break;

            case THREAD_TERMINATE_REQ:
                Display_printf(display, 0, 0,
                               "[MQTT Thread] Terminating MQTT Client");

                SET_STATUS_BIT(App_CB.status, AppStatusBits_MqttStopInProgress);
                Mqtt_ClientStop(1);
                pthread_exit(0);
                return NULL;

            default:
                break;
            }
            break;
        }
        }
    }
}

//*****************************************************************************
//
//! MQTT_SendMsgToQueue - Utility function that receive msgQueue parameter and
//! tries to push it the queue with minimal time for timeout of 0.
//! If the queue isn't full the parameter will be stored and the function
//! will return 0.
//! If the queue is full and the timeout expired (because the timeout parameter
//! is 0 it will expire immediately), the parameter is thrown away and the
//! function will return -1 as an error for full queue.
//!
//! \param[in] struct msgQueue *queueElement
//!
//! \return 0 on success, -1 on error
//
//*****************************************************************************
int32_t MQTT_SendMsgToQueue(struct msgQueue *queueElement)
{
    struct timespec abstime = { 0 };

    clock_gettime(CLOCK_REALTIME, &abstime);

    if (App_CB.mqttQueue)
    {
        /* send message to the queue                                         */
        if (mq_timedsend(App_CB.mqttQueue, (char *) queueElement,
                         sizeof(struct msgQueue), 0, &abstime) == 0)
        {
            return 0;
        }
    }
    return -1;
}

int32_t MqttClient_start()
{
    int32_t lRetVal = -1;

    int32_t threadArg = 100;
    pthread_attr_t pAttrs;
    struct sched_param priParam;

    MqttClientExample_params.clientId = ClientId;
    MqttClientExample_params.connParams = &Mqtt_ClientCtx;
    MqttClientExample_params.mqttMode31 = MQTT_3_1;
    MqttClientExample_params.blockingSend = true;

    /* Initialize MQTT client lib                                             */
    App_CB.mqttClientHandle = MQTTClient_create(MqttClientCallback,
                                                &MqttClientExample_params);
    if (App_CB.mqttClientHandle == NULL)
    {
        /* Lib initialization failed */
        Display_printf(display, 0, 0,
                       "\t [MQTT Thread] Error - MQTT client handle is null");
        return -1;
    }

    /* Open Client Receive Thread start the receive task. Set priority and    */
    /* stack size attributes                                                  */
    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 4;
    lRetVal = pthread_attr_setschedparam(&pAttrs, &priParam);
    lRetVal |= pthread_attr_setstacksize(&pAttrs, MQTT_CLIENT_RX_TASK_SIZE);
    lRetVal |= pthread_attr_setdetachstate(&pAttrs, PTHREAD_CREATE_DETACHED);
    lRetVal |= pthread_create(&g_rx_task_hndl, &pAttrs, MqttClientThread,
                              (void *) &threadArg);
    if (lRetVal != 0)
    {
        Display_printf(display, 0, 0,
                       "[MQTT Thread] Client Thread create failed");
        return -1;
    }

    /* setting will parameters                                                */
    MQTTClient_set(App_CB.mqttClientHandle, MQTTClient_WILL_PARAM, &will_param,
                   sizeof(will_param));
    MQTTClient_set(App_CB.mqttClientHandle, MQTTClient_KEEPALIVE_TIME,
                   &mqttKeepAliveTimeout, sizeof(mqttKeepAliveTimeout));

    return 0;
}

//*****************************************************************************
//!
//! MQTT Client stop - Unsubscribe from the subscription topics and exit the
//! MQTT client lib.
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************

void Mqtt_ClientStop(uint8_t disconnect)
{
    uint32_t iCount;

    MQTTClient_UnsubscribeParams subscriptionInfo[SUBSCRIPTION_TOPIC_COUNT];

    for (iCount = 0; iCount < SUBSCRIPTION_TOPIC_COUNT; iCount++)
    {
        subscriptionInfo[iCount].topic = topic[iCount];
    }

    MQTTClient_unsubscribe(App_CB.mqttClientHandle, subscriptionInfo,
    SUBSCRIPTION_TOPIC_COUNT);
    for (iCount = 0; iCount < SUBSCRIPTION_TOPIC_COUNT; iCount++)
    {
        Display_printf(display, 0, 0,
                       "[MQTT Thread] Unsubscribed from the topic %s",
                       topic[iCount]);
    }

    CLR_STATUS_BIT(App_CB.status, AppStatusBits_MqttConnected);
    CLR_STATUS_BIT(App_CB.status, AppStatusBits_MqttInitialized);

    /* exiting the Client library                                             */
    MQTTClient_delete(App_CB.mqttClientHandle);

    Display_printf(display, 0, 0, "[MQTT Thread] MQTT Client Handle deleted");

    CLR_STATUS_BIT(App_CB.status, AppStatusBits_MqttStopInProgress);
}

void *MqttClientThread(void * pvParameters)
{
    struct msgQueue queueElement;
    struct msgQueue queueElemRecv;

    MQTTClient_run((MQTTClient_Handle) pvParameters);

    /* Check if MQTT thread is already being stopped */
    if(!GET_STATUS_BIT(App_CB.status, AppStatusBits_MqttStopInProgress))
    {
        SET_STATUS_BIT(App_CB.status, AppStatusBits_MqttStopInProgress);

        queueElement.event = LOCAL_CLIENT_DISCONNECTION;
        queueElement.msgPtr = NULL;

        /* write message indicating disconnect Broker message.                    */
        if (MQTT_SendMsgToQueue(&queueElement))
        {
            Display_printf(
                    display,
                    0,
                    0,
                    "\t [MQTT Thread] Queue is full, throw first msg and send the new one");
            mq_receive(App_CB.mqttQueue, (char*) &queueElemRecv,
                       sizeof(struct msgQueue), NULL);
            MQTT_SendMsgToQueue(&queueElement);
        }
    }

    pthread_exit(0);

    return NULL;
}
